Spaces:
Running
Running
Upload 3 files
Browse files- .gitattributes +1 -0
- README.md +126 -0
- index.html +413 -0
- sensorik.png +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
sensorik.png filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Sensor Data Analyzer
|
| 3 |
+
emoji: 📡
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: static
|
| 7 |
+
app_file: index.html
|
| 8 |
+
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
short_description: Sensordaten lokal analysieren und Anomalien erkennen.
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
# Sensor Data Analyzer
|
| 14 |
+
|
| 15 |
+
**Sensordaten direkt im Browser analysieren, visualisieren und Auffälligkeiten erkennen.**
|
| 16 |
+
|
| 17 |
+
Sensor Data Analyzer ist ein kostenloses Tool der unabhängigen Hugging-Face-Organisation **Sensorik**.
|
| 18 |
+
|
| 19 |
+
Das Tool eignet sich für CSV-basierte Messreihen aus Bereichen wie:
|
| 20 |
+
|
| 21 |
+
- Temperatur
|
| 22 |
+
- Luftfeuchtigkeit
|
| 23 |
+
- Druck
|
| 24 |
+
- Strom und Spannung
|
| 25 |
+
- Vibration
|
| 26 |
+
- Maschinenzustände
|
| 27 |
+
- IoT
|
| 28 |
+
- Umweltmessung
|
| 29 |
+
- Energie
|
| 30 |
+
- Labor- und Versuchsdaten
|
| 31 |
+
|
| 32 |
+
## Funktionen
|
| 33 |
+
|
| 34 |
+
- CSV-Dateien lokal im Browser öffnen
|
| 35 |
+
- Trennzeichen automatisch erkennen
|
| 36 |
+
- numerische Messspalten automatisch erkennen
|
| 37 |
+
- Zeit-/Indexspalte auswählen
|
| 38 |
+
- Messwerte visualisieren
|
| 39 |
+
- Mittelwert, Median, Minimum, Maximum und Standardabweichung berechnen
|
| 40 |
+
- gleitenden Mittelwert anzeigen
|
| 41 |
+
- Anomalien über robuste MAD-Analyse erkennen
|
| 42 |
+
- Empfindlichkeit der Erkennung einstellen
|
| 43 |
+
- auffällige Messwerte tabellarisch anzeigen
|
| 44 |
+
- erkannte Anomalien als CSV exportieren
|
| 45 |
+
- Beispieldatensatz direkt im Tool laden
|
| 46 |
+
- responsive Oberfläche für Desktop und Mobilgeräte
|
| 47 |
+
|
| 48 |
+
## Datenschutz
|
| 49 |
+
|
| 50 |
+
**Die Verarbeitung erfolgt vollständig lokal im Browser.**
|
| 51 |
+
|
| 52 |
+
Dieser Static Space überträgt hochgeladene CSV-Dateien oder Messwerte nicht an einen Server.
|
| 53 |
+
|
| 54 |
+
## Warum MAD?
|
| 55 |
+
|
| 56 |
+
Für die automatische Auffälligkeitserkennung verwendet das Tool eine robuste Methode auf Basis von
|
| 57 |
+
**Median Absolute Deviation (MAD)**.
|
| 58 |
+
|
| 59 |
+
Sie ist gegenüber einzelnen Extremwerten häufig robuster als ein klassischer Z-Score.
|
| 60 |
+
|
| 61 |
+
Eine erkannte Anomalie bedeutet jedoch **nicht automatisch**, dass ein Sensor oder eine Maschine defekt ist.
|
| 62 |
+
|
| 63 |
+
## Beispiel
|
| 64 |
+
|
| 65 |
+
Eine Messreihe wie:
|
| 66 |
+
|
| 67 |
+
```text
|
| 68 |
+
timestamp,temperature
|
| 69 |
+
10:00,68.2
|
| 70 |
+
10:01,68.5
|
| 71 |
+
10:02,68.1
|
| 72 |
+
10:03,92.7
|
| 73 |
+
10:04,68.4
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
kann den Messwert `92.7` als auffällig markieren.
|
| 77 |
+
|
| 78 |
+
Die Ursache muss anschließend fachlich untersucht werden.
|
| 79 |
+
|
| 80 |
+
## Mögliche Einsatzbereiche
|
| 81 |
+
|
| 82 |
+
- schnelle Erstprüfung von Sensordaten
|
| 83 |
+
- Datenqualität
|
| 84 |
+
- IoT-Debugging
|
| 85 |
+
- Maschinenüberwachung
|
| 86 |
+
- Predictive-Maintenance-Prototyping
|
| 87 |
+
- Umwelt- und Energiedaten
|
| 88 |
+
- Lehre und Demonstration
|
| 89 |
+
- Vorbereitung von Machine-Learning-Datasets
|
| 90 |
+
|
| 91 |
+
## Einschränkungen
|
| 92 |
+
|
| 93 |
+
Das Tool ist bewusst leichtgewichtig.
|
| 94 |
+
|
| 95 |
+
Es berücksichtigt unter anderem nicht automatisch:
|
| 96 |
+
|
| 97 |
+
- physikalische Grenzwerte
|
| 98 |
+
- Sensor-Kalibrierung
|
| 99 |
+
- saisonale Effekte
|
| 100 |
+
- komplexe Trendbrüche
|
| 101 |
+
- mehrere gekoppelte Sensorsignale
|
| 102 |
+
- Maschinen- oder Prozesskontext
|
| 103 |
+
|
| 104 |
+
Für sicherheitskritische oder industrielle Entscheidungen ist eine fachliche Validierung erforderlich.
|
| 105 |
+
|
| 106 |
+
## Wichtiger Hinweis
|
| 107 |
+
|
| 108 |
+
Sensor Data Analyzer ist ein **Analyse-, Lern- und Prototyping-Werkzeug**.
|
| 109 |
+
|
| 110 |
+
Er ist kein zertifiziertes:
|
| 111 |
+
|
| 112 |
+
- Messsystem
|
| 113 |
+
- Diagnosesystem
|
| 114 |
+
- Sicherheitssystem
|
| 115 |
+
- Steuerungssystem
|
| 116 |
+
- Predictive-Maintenance-System
|
| 117 |
+
|
| 118 |
+
Ergebnisse können unvollständig, fehlerhaft oder kontextabhängig sein.
|
| 119 |
+
|
| 120 |
+
## Unabhängigkeit
|
| 121 |
+
|
| 122 |
+
**Sensorik** ist eine unabhängige Hugging-Face-Community-Organisation und nicht mit einem Sensorhersteller, Industrieunternehmen, Forschungsinstitut oder Hugging Face offiziell verbunden.
|
| 123 |
+
|
| 124 |
+
---
|
| 125 |
+
|
| 126 |
+
**Messen. Verstehen. Erkennen. Intelligent reagieren.**
|
index.html
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="de">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
| 6 |
+
<meta name="description" content="Sensor Data Analyzer: CSV-Sensordaten lokal im Browser analysieren, visualisieren und Anomalien erkennen.">
|
| 7 |
+
<title>Sensor Data Analyzer</title>
|
| 8 |
+
<style>
|
| 9 |
+
:root{
|
| 10 |
+
--bg:#06131f;--panel:#0b2233;--panel2:#0f2c40;--line:#24485e;
|
| 11 |
+
--text:#eef7fb;--muted:#9db5c2;--cyan:#35d7e7;--blue:#4b8cff;
|
| 12 |
+
--green:#65d69b;--yellow:#ffd166;--red:#ff7e8a;--shadow:0 18px 60px rgba(0,0,0,.28);
|
| 13 |
+
}
|
| 14 |
+
*{box-sizing:border-box}
|
| 15 |
+
body{
|
| 16 |
+
margin:0;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
|
| 17 |
+
background:
|
| 18 |
+
radial-gradient(circle at 8% 0%,rgba(53,215,231,.13),transparent 29rem),
|
| 19 |
+
radial-gradient(circle at 92% 8%,rgba(75,140,255,.13),transparent 30rem),
|
| 20 |
+
var(--bg);
|
| 21 |
+
color:var(--text);line-height:1.5;
|
| 22 |
+
}
|
| 23 |
+
.wrap{max-width:1200px;margin:auto;padding:24px 18px 60px}
|
| 24 |
+
.hero,.panel{
|
| 25 |
+
border:1px solid var(--line);background:rgba(11,34,51,.95);border-radius:22px;
|
| 26 |
+
box-shadow:var(--shadow)
|
| 27 |
+
}
|
| 28 |
+
.hero{padding:28px;margin-bottom:18px;background:linear-gradient(135deg,rgba(15,44,64,.96),rgba(6,19,31,.97))}
|
| 29 |
+
.badge{display:inline-flex;gap:8px;align-items:center;padding:7px 11px;border-radius:999px;
|
| 30 |
+
background:rgba(53,215,231,.09);border:1px solid rgba(53,215,231,.25);color:#c3fbff;font-size:13px;font-weight:800}
|
| 31 |
+
h1{font-size:clamp(33px,5vw,58px);margin:15px 0 9px;letter-spacing:-.04em;line-height:1.02}
|
| 32 |
+
.hero p{color:var(--muted);max-width:820px;font-size:17px;margin:0}
|
| 33 |
+
.actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:20px}
|
| 34 |
+
button,.file-label{
|
| 35 |
+
border:0;border-radius:12px;padding:11px 15px;font-weight:800;cursor:pointer;font-size:14px;
|
| 36 |
+
transition:.15s transform,.15s opacity;display:inline-flex;align-items:center;gap:7px
|
| 37 |
+
}
|
| 38 |
+
button:hover,.file-label:hover{transform:translateY(-1px)}
|
| 39 |
+
.primary{background:linear-gradient(135deg,var(--cyan),var(--blue));color:#04111c}
|
| 40 |
+
.secondary{background:#16384e;color:var(--text);border:1px solid var(--line)}
|
| 41 |
+
.ghost{background:transparent;color:var(--muted);border:1px solid var(--line)}
|
| 42 |
+
input[type=file]{display:none}
|
| 43 |
+
.privacy{display:flex;gap:10px;align-items:flex-start;margin-top:18px;border:1px solid rgba(101,214,155,.25);
|
| 44 |
+
background:rgba(101,214,155,.06);padding:13px 15px;border-radius:15px;font-size:13px}
|
| 45 |
+
.privacy strong{color:#baffd4}
|
| 46 |
+
.grid{display:grid;grid-template-columns:330px 1fr;gap:18px}
|
| 47 |
+
.panel{padding:19px}
|
| 48 |
+
.panel h2{margin:0 0 5px;font-size:19px}
|
| 49 |
+
.sub{font-size:13px;color:var(--muted);margin-bottom:15px}
|
| 50 |
+
.control{margin-bottom:14px}
|
| 51 |
+
.control label{display:block;font-size:12px;font-weight:800;color:#cfe0e8;margin-bottom:6px}
|
| 52 |
+
select,input[type=range],input[type=number]{
|
| 53 |
+
width:100%;border:1px solid var(--line);border-radius:10px;background:#071a28;color:var(--text);padding:10px
|
| 54 |
+
}
|
| 55 |
+
input[type=range]{padding:0}
|
| 56 |
+
.range-row{display:grid;grid-template-columns:1fr 52px;gap:9px;align-items:center}
|
| 57 |
+
.range-value{background:#071a28;border:1px solid var(--line);border-radius:9px;padding:7px;text-align:center;font-size:12px}
|
| 58 |
+
.fileinfo{padding:11px;border:1px solid var(--line);background:#071a28;border-radius:12px;color:var(--muted);font-size:12px;margin:12px 0}
|
| 59 |
+
.metrics{display:grid;grid-template-columns:repeat(6,1fr);gap:10px;margin-bottom:16px}
|
| 60 |
+
.metric{border:1px solid var(--line);background:#081d2b;border-radius:14px;padding:13px;min-width:0}
|
| 61 |
+
.metric span{display:block;color:var(--muted);font-size:11px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 62 |
+
.metric strong{font-size:20px;letter-spacing:-.03em}
|
| 63 |
+
.chart-wrap{position:relative;height:420px;border:1px solid var(--line);border-radius:16px;background:#061925;overflow:hidden}
|
| 64 |
+
canvas{width:100%;height:100%;display:block}
|
| 65 |
+
.chart-note{font-size:12px;color:var(--muted);margin-top:9px}
|
| 66 |
+
.legend{display:flex;gap:14px;flex-wrap:wrap;font-size:12px;color:var(--muted);margin-top:8px}
|
| 67 |
+
.dot{width:9px;height:9px;border-radius:50%;display:inline-block;margin-right:5px}
|
| 68 |
+
.table-wrap{overflow:auto;max-height:330px;border:1px solid var(--line);border-radius:13px;margin-top:12px}
|
| 69 |
+
table{width:100%;border-collapse:collapse;font-size:12px}
|
| 70 |
+
th,td{padding:9px 10px;border-bottom:1px solid rgba(36,72,94,.7);text-align:left;white-space:nowrap}
|
| 71 |
+
th{position:sticky;top:0;background:#102c40;z-index:1}
|
| 72 |
+
.empty{padding:26px;color:var(--muted);text-align:center}
|
| 73 |
+
.notice{margin-top:18px;padding:15px 16px;border-radius:15px;border:1px solid rgba(255,209,102,.27);
|
| 74 |
+
background:rgba(255,209,102,.06);font-size:13px;color:#dbe7ec}
|
| 75 |
+
footer{text-align:center;color:#6f8998;font-size:12px;margin-top:24px}
|
| 76 |
+
.status-good{color:var(--green)} .status-warn{color:var(--yellow)}
|
| 77 |
+
@media(max-width:900px){
|
| 78 |
+
.grid{grid-template-columns:1fr}.metrics{grid-template-columns:repeat(3,1fr)}
|
| 79 |
+
}
|
| 80 |
+
@media(max-width:560px){
|
| 81 |
+
.wrap{padding:12px 10px 40px}.hero,.panel{padding:16px;border-radius:16px}
|
| 82 |
+
.metrics{grid-template-columns:repeat(2,1fr)}.chart-wrap{height:330px}
|
| 83 |
+
}
|
| 84 |
+
</style>
|
| 85 |
+
</head>
|
| 86 |
+
<body>
|
| 87 |
+
<div class="wrap">
|
| 88 |
+
<section class="hero">
|
| 89 |
+
<span class="badge">📡 Sensorik · Local-first Sensor Analysis</span>
|
| 90 |
+
<h1>Sensor Data Analyzer</h1>
|
| 91 |
+
<p>CSV-Messdaten öffnen, Sensorverläufe visualisieren, statistisch zusammenfassen und auffällige Messwerte mit robuster MAD-Analyse erkennen.</p>
|
| 92 |
+
<div class="actions">
|
| 93 |
+
<label class="file-label primary" for="fileInput">📂 CSV öffnen</label>
|
| 94 |
+
<input id="fileInput" type="file" accept=".csv,text/csv">
|
| 95 |
+
<button class="secondary" onclick="loadDemo()">Beispieldaten laden</button>
|
| 96 |
+
<button class="ghost" onclick="downloadAnomalies()">Anomalien exportieren</button>
|
| 97 |
+
</div>
|
| 98 |
+
<div class="privacy">
|
| 99 |
+
<div>🔒</div>
|
| 100 |
+
<div><strong>Lokale Verarbeitung:</strong> CSV-Dateien und Messwerte bleiben in deinem Browser. Dieser Static Space überträgt keine hochgeladenen Sensordaten an einen Server.</div>
|
| 101 |
+
</div>
|
| 102 |
+
</section>
|
| 103 |
+
|
| 104 |
+
<div class="grid">
|
| 105 |
+
<aside class="panel">
|
| 106 |
+
<h2>Analyse konfigurieren</h2>
|
| 107 |
+
<div class="sub">Messspalte und Erkennung anpassen</div>
|
| 108 |
+
|
| 109 |
+
<div class="fileinfo" id="fileInfo">Noch keine Daten geladen.</div>
|
| 110 |
+
|
| 111 |
+
<div class="control">
|
| 112 |
+
<label for="xColumn">Zeit / X-Achse</label>
|
| 113 |
+
<select id="xColumn"></select>
|
| 114 |
+
</div>
|
| 115 |
+
|
| 116 |
+
<div class="control">
|
| 117 |
+
<label for="yColumn">Messwert</label>
|
| 118 |
+
<select id="yColumn"></select>
|
| 119 |
+
</div>
|
| 120 |
+
|
| 121 |
+
<div class="control">
|
| 122 |
+
<label for="threshold">Anomalie-Empfindlichkeit (MAD)</label>
|
| 123 |
+
<div class="range-row">
|
| 124 |
+
<input id="threshold" type="range" min="1.5" max="6" step=".1" value="3.5">
|
| 125 |
+
<div class="range-value" id="thresholdValue">3.5</div>
|
| 126 |
+
</div>
|
| 127 |
+
</div>
|
| 128 |
+
|
| 129 |
+
<div class="control">
|
| 130 |
+
<label for="smooth">Gleitender Mittelwert</label>
|
| 131 |
+
<div class="range-row">
|
| 132 |
+
<input id="smooth" type="range" min="1" max="30" step="1" value="5">
|
| 133 |
+
<div class="range-value" id="smoothValue">5</div>
|
| 134 |
+
</div>
|
| 135 |
+
</div>
|
| 136 |
+
|
| 137 |
+
<div class="control">
|
| 138 |
+
<label for="limit">Max. Punkte im Diagramm</label>
|
| 139 |
+
<select id="limit">
|
| 140 |
+
<option value="500">500</option>
|
| 141 |
+
<option value="1000" selected>1.000</option>
|
| 142 |
+
<option value="2500">2.500</option>
|
| 143 |
+
<option value="5000">5.000</option>
|
| 144 |
+
</select>
|
| 145 |
+
</div>
|
| 146 |
+
|
| 147 |
+
<button class="secondary" style="width:100%;justify-content:center" onclick="analyze()">Analyse aktualisieren</button>
|
| 148 |
+
</aside>
|
| 149 |
+
|
| 150 |
+
<main>
|
| 151 |
+
<section class="panel">
|
| 152 |
+
<h2>Messreihe</h2>
|
| 153 |
+
<div class="sub">Statistik und Verlauf der ausgewählten Sensorgröße</div>
|
| 154 |
+
<div class="metrics" id="metrics">
|
| 155 |
+
<div class="metric"><span>Messwerte</span><strong>–</strong></div>
|
| 156 |
+
<div class="metric"><span>Mittelwert</span><strong>–</strong></div>
|
| 157 |
+
<div class="metric"><span>Median</span><strong>–</strong></div>
|
| 158 |
+
<div class="metric"><span>Minimum</span><strong>–</strong></div>
|
| 159 |
+
<div class="metric"><span>Maximum</span><strong>–</strong></div>
|
| 160 |
+
<div class="metric"><span>Anomalien</span><strong>–</strong></div>
|
| 161 |
+
</div>
|
| 162 |
+
|
| 163 |
+
<div class="chart-wrap"><canvas id="chart"></canvas></div>
|
| 164 |
+
<div class="legend">
|
| 165 |
+
<span><i class="dot" style="background:#35d7e7"></i>Messwert</span>
|
| 166 |
+
<span><i class="dot" style="background:#ffd166"></i>Gleitender Mittelwert</span>
|
| 167 |
+
<span><i class="dot" style="background:#ff7e8a"></i>Erkannte Anomalie</span>
|
| 168 |
+
</div>
|
| 169 |
+
<div class="chart-note" id="chartNote">Lade eine CSV-Datei oder Beispieldaten.</div>
|
| 170 |
+
</section>
|
| 171 |
+
|
| 172 |
+
<section class="panel" style="margin-top:18px">
|
| 173 |
+
<h2>Auffällige Messwerte</h2>
|
| 174 |
+
<div class="sub">Robuste MAD-Erkennung – fachliche Interpretation bleibt erforderlich</div>
|
| 175 |
+
<div class="table-wrap" id="anomalyTable"><div class="empty">Noch keine Analyse vorhanden.</div></div>
|
| 176 |
+
</section>
|
| 177 |
+
</main>
|
| 178 |
+
</div>
|
| 179 |
+
|
| 180 |
+
<div class="notice">
|
| 181 |
+
<strong>Hinweis:</strong> Eine statistische Anomalie ist nicht automatisch ein Sensorfehler, Defekt oder Sicherheitsereignis. Prozesszustand, Kalibrierung, physikalische Grenzwerte und Messbedingungen müssen berücksichtigt werden. Das Tool ist nicht für sicherheitskritische Entscheidungen zertifiziert.
|
| 182 |
+
</div>
|
| 183 |
+
<footer>Sensorik · Independent Hugging Face Community Project</footer>
|
| 184 |
+
</div>
|
| 185 |
+
|
| 186 |
+
<script>
|
| 187 |
+
let rows=[], headers=[], anomalies=[], currentValues=[], currentLabels=[];
|
| 188 |
+
|
| 189 |
+
const $=id=>document.getElementById(id);
|
| 190 |
+
$("threshold").addEventListener("input",()=>{$("thresholdValue").textContent=$("threshold").value; analyze();});
|
| 191 |
+
$("smooth").addEventListener("input",()=>{$("smoothValue").textContent=$("smooth").value; analyze();});
|
| 192 |
+
$("xColumn").addEventListener("change",analyze);
|
| 193 |
+
$("yColumn").addEventListener("change",analyze);
|
| 194 |
+
$("limit").addEventListener("change",analyze);
|
| 195 |
+
$("fileInput").addEventListener("change",handleFile);
|
| 196 |
+
|
| 197 |
+
function detectDelimiter(line){
|
| 198 |
+
const options=[",",";","\t","|"];
|
| 199 |
+
return options.map(d=>[d,(line.split(d).length-1)]).sort((a,b)=>b[1]-a[1])[0][0];
|
| 200 |
+
}
|
| 201 |
+
function parseCSV(text){
|
| 202 |
+
const lines=text.replace(/\r/g,"").split("\n").filter(x=>x.trim().length);
|
| 203 |
+
if(lines.length<2) throw new Error("CSV enthält zu wenige Zeilen.");
|
| 204 |
+
const delim=detectDelimiter(lines[0]);
|
| 205 |
+
const parseLine=line=>{
|
| 206 |
+
let out=[],cur="",quote=false;
|
| 207 |
+
for(let i=0;i<line.length;i++){
|
| 208 |
+
const c=line[i];
|
| 209 |
+
if(c==='"'){
|
| 210 |
+
if(quote && line[i+1]==='"'){cur+='"';i++}
|
| 211 |
+
else quote=!quote;
|
| 212 |
+
} else if(c===delim && !quote){out.push(cur.trim());cur=""}
|
| 213 |
+
else cur+=c;
|
| 214 |
+
}
|
| 215 |
+
out.push(cur.trim()); return out;
|
| 216 |
+
};
|
| 217 |
+
const h=parseLine(lines[0]).map((x,i)=>x||("column_"+(i+1)));
|
| 218 |
+
const data=lines.slice(1).map(line=>{
|
| 219 |
+
const vals=parseLine(line),o={};
|
| 220 |
+
h.forEach((k,i)=>o[k]=vals[i]??"");
|
| 221 |
+
return o;
|
| 222 |
+
});
|
| 223 |
+
return {headers:h,rows:data,delimiter:delim};
|
| 224 |
+
}
|
| 225 |
+
function toNum(v){
|
| 226 |
+
if(v===null||v===undefined||v==="") return NaN;
|
| 227 |
+
let s=String(v).trim().replace(/\s/g,"");
|
| 228 |
+
if(/^[-+]?\d{1,3}(\.\d{3})*,\d+$/.test(s)) s=s.replace(/\./g,"").replace(",",".");
|
| 229 |
+
else if(/^[-+]?\d+,\d+$/.test(s)) s=s.replace(",",".");
|
| 230 |
+
const n=Number(s);
|
| 231 |
+
return Number.isFinite(n)?n:NaN;
|
| 232 |
+
}
|
| 233 |
+
function numericShare(col){
|
| 234 |
+
if(!rows.length)return 0;
|
| 235 |
+
const vals=rows.slice(0,Math.min(300,rows.length));
|
| 236 |
+
return vals.filter(r=>Number.isFinite(toNum(r[col]))).length/vals.length;
|
| 237 |
+
}
|
| 238 |
+
function setupSelectors(){
|
| 239 |
+
$("xColumn").innerHTML=`<option value="__index__">Zeilenindex</option>`+headers.map(h=>`<option value="${esc(h)}">${esc(h)}</option>`).join("");
|
| 240 |
+
const nums=headers.filter(h=>numericShare(h)>.6);
|
| 241 |
+
$("yColumn").innerHTML=nums.map(h=>`<option value="${esc(h)}">${esc(h)}</option>`).join("");
|
| 242 |
+
if(nums.length) $("yColumn").value=nums[0];
|
| 243 |
+
const likelyTime=headers.find(h=>/time|date|datum|zeit|timestamp/i.test(h));
|
| 244 |
+
if(likelyTime) $("xColumn").value=likelyTime;
|
| 245 |
+
}
|
| 246 |
+
function esc(s){return String(s).replace(/[&<>"']/g,m=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[m]))}
|
| 247 |
+
|
| 248 |
+
async function handleFile(e){
|
| 249 |
+
const f=e.target.files[0]; if(!f)return;
|
| 250 |
+
try{
|
| 251 |
+
const text=await f.text();
|
| 252 |
+
const p=parseCSV(text);
|
| 253 |
+
headers=p.headers; rows=p.rows;
|
| 254 |
+
$("fileInfo").innerHTML=`<strong>${esc(f.name)}</strong><br>${rows.length.toLocaleString("de-DE")} Zeilen · ${headers.length} Spalten · Trennzeichen: <code>${p.delimiter===" "?"Tab":esc(p.delimiter)}</code>`;
|
| 255 |
+
setupSelectors(); analyze();
|
| 256 |
+
}catch(err){
|
| 257 |
+
$("fileInfo").textContent="Fehler: "+err.message;
|
| 258 |
+
}
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
function loadDemo(){
|
| 262 |
+
headers=["timestamp","temperature","vibration","pressure"];
|
| 263 |
+
rows=[];
|
| 264 |
+
const start=new Date("2026-09-16T08:00:00");
|
| 265 |
+
for(let i=0;i<360;i++){
|
| 266 |
+
const t=new Date(start.getTime()+i*60000);
|
| 267 |
+
let temp=68 + Math.sin(i/25)*1.8 + Math.sin(i/7)*.25;
|
| 268 |
+
let vib=2.1 + Math.sin(i/12)*.18 + Math.cos(i/5)*.08;
|
| 269 |
+
let pressure=5.2 + Math.sin(i/50)*.12;
|
| 270 |
+
if([91,92,93].includes(i)) temp+=12;
|
| 271 |
+
if([218,219].includes(i)) vib+=1.7;
|
| 272 |
+
if(i===305) pressure-=1.1;
|
| 273 |
+
rows.push({
|
| 274 |
+
timestamp:t.toISOString().slice(0,16).replace("T"," "),
|
| 275 |
+
temperature:temp.toFixed(2),
|
| 276 |
+
vibration:vib.toFixed(3),
|
| 277 |
+
pressure:pressure.toFixed(3)
|
| 278 |
+
});
|
| 279 |
+
}
|
| 280 |
+
$("fileInfo").innerHTML="<strong>Beispieldaten</strong><br>360 Zeilen · 3 Sensorsignale · künstliche Anomalien";
|
| 281 |
+
setupSelectors(); $("xColumn").value="timestamp"; $("yColumn").value="temperature"; analyze();
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
function median(arr){
|
| 285 |
+
if(!arr.length)return NaN;
|
| 286 |
+
const a=[...arr].sort((x,y)=>x-y),m=Math.floor(a.length/2);
|
| 287 |
+
return a.length%2?a[m]:(a[m-1]+a[m])/2;
|
| 288 |
+
}
|
| 289 |
+
function stats(v){
|
| 290 |
+
if(!v.length)return {};
|
| 291 |
+
const mean=v.reduce((a,b)=>a+b,0)/v.length;
|
| 292 |
+
const med=median(v);
|
| 293 |
+
const sd=Math.sqrt(v.reduce((s,x)=>s+(x-mean)**2,0)/Math.max(1,v.length-1));
|
| 294 |
+
return {n:v.length,mean,median:med,min:Math.min(...v),max:Math.max(...v),sd};
|
| 295 |
+
}
|
| 296 |
+
function madAnomalies(vals,threshold){
|
| 297 |
+
const med=median(vals);
|
| 298 |
+
const abs=vals.map(x=>Math.abs(x-med));
|
| 299 |
+
const mad=median(abs);
|
| 300 |
+
if(!Number.isFinite(mad)||mad===0) return vals.map(()=>({score:0,is:false}));
|
| 301 |
+
return vals.map(x=>{
|
| 302 |
+
const score=.6745*(x-med)/mad;
|
| 303 |
+
return {score,is:Math.abs(score)>threshold};
|
| 304 |
+
});
|
| 305 |
+
}
|
| 306 |
+
function movingAverage(vals,w){
|
| 307 |
+
if(w<=1)return [...vals];
|
| 308 |
+
const out=[],q=[];let sum=0;
|
| 309 |
+
for(const v of vals){
|
| 310 |
+
q.push(v);sum+=v;
|
| 311 |
+
if(q.length>w)sum-=q.shift();
|
| 312 |
+
out.push(sum/q.length);
|
| 313 |
+
}
|
| 314 |
+
return out;
|
| 315 |
+
}
|
| 316 |
+
function fmt(n){
|
| 317 |
+
if(!Number.isFinite(n))return "–";
|
| 318 |
+
return new Intl.NumberFormat("de-DE",{maximumFractionDigits:3}).format(n);
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
function analyze(){
|
| 322 |
+
if(!rows.length || !$("yColumn").value){renderEmpty();return}
|
| 323 |
+
const xcol=$("xColumn").value,ycol=$("yColumn").value,lim=parseInt($("limit").value);
|
| 324 |
+
let temp=[];
|
| 325 |
+
rows.forEach((r,i)=>{
|
| 326 |
+
const y=toNum(r[ycol]);
|
| 327 |
+
if(Number.isFinite(y)) temp.push({label:xcol==="__index__"?(i+1):r[xcol],value:y,row:i});
|
| 328 |
+
});
|
| 329 |
+
if(!temp.length){renderEmpty();return}
|
| 330 |
+
const step=Math.max(1,Math.ceil(temp.length/lim));
|
| 331 |
+
const sampled=temp.filter((_,i)=>i%step===0 || i===temp.length-1);
|
| 332 |
+
currentValues=sampled.map(x=>x.value);
|
| 333 |
+
currentLabels=sampled.map(x=>x.label);
|
| 334 |
+
const s=stats(currentValues), threshold=parseFloat($("threshold").value);
|
| 335 |
+
const flags=madAnomalies(currentValues,threshold);
|
| 336 |
+
const smooth=movingAverage(currentValues,parseInt($("smooth").value));
|
| 337 |
+
anomalies=sampled.map((x,i)=>({...x,score:flags[i].score,is:flags[i].is})).filter(x=>x.is);
|
| 338 |
+
|
| 339 |
+
$("metrics").innerHTML=[
|
| 340 |
+
["Messwerte",s.n.toLocaleString("de-DE")],
|
| 341 |
+
["Mittelwert",fmt(s.mean)],
|
| 342 |
+
["Median",fmt(s.median)],
|
| 343 |
+
["Minimum",fmt(s.min)],
|
| 344 |
+
["Maximum",fmt(s.max)],
|
| 345 |
+
["Anomalien",anomalies.length.toLocaleString("de-DE")]
|
| 346 |
+
].map(x=>`<div class="metric"><span>${x[0]}</span><strong>${x[1]}</strong></div>`).join("");
|
| 347 |
+
|
| 348 |
+
drawChart(currentValues,smooth,flags);
|
| 349 |
+
renderAnomalies();
|
| 350 |
+
$("chartNote").innerHTML=`Spalte <strong>${esc(ycol)}</strong> · ${temp.length.toLocaleString("de-DE")} gültige Werte`+
|
| 351 |
+
(step>1?` · Diagramm auf ca. ${sampled.length.toLocaleString("de-DE")} Punkte reduziert`:"")+
|
| 352 |
+
` · MAD-Schwelle ${threshold}`;
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
function renderEmpty(){
|
| 356 |
+
$("chartNote").textContent="Lade eine CSV-Datei oder Beispieldaten.";
|
| 357 |
+
$("anomalyTable").innerHTML='<div class="empty">Noch keine Analyse vorhanden.</div>';
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
function renderAnomalies(){
|
| 361 |
+
if(!anomalies.length){
|
| 362 |
+
$("anomalyTable").innerHTML='<div class="empty status-good">Keine Auffälligkeiten mit der aktuellen MAD-Schwelle erkannt.</div>';
|
| 363 |
+
return;
|
| 364 |
+
}
|
| 365 |
+
$("anomalyTable").innerHTML=`<table><thead><tr><th>#</th><th>X / Zeit</th><th>Messwert</th><th>Robuster Score</th></tr></thead><tbody>`+
|
| 366 |
+
anomalies.slice(0,500).map((a,i)=>`<tr><td>${i+1}</td><td>${esc(a.label)}</td><td>${fmt(a.value)}</td><td>${fmt(a.score)}</td></tr>`).join("")+
|
| 367 |
+
`</tbody></table>`+(anomalies.length>500?`<div class="empty">${anomalies.length-500} weitere Anomalien nicht angezeigt.</div>`:"");
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
function drawChart(vals,smooth,flags){
|
| 371 |
+
const c=$("chart"),rect=c.parentElement.getBoundingClientRect(),dpr=window.devicePixelRatio||1;
|
| 372 |
+
c.width=Math.max(300,Math.floor(rect.width*dpr)); c.height=Math.max(260,Math.floor(rect.height*dpr));
|
| 373 |
+
const ctx=c.getContext("2d");ctx.scale(dpr,dpr);
|
| 374 |
+
const W=rect.width,H=rect.height,p={l:54,r:18,t:18,b:34};
|
| 375 |
+
ctx.clearRect(0,0,W,H);
|
| 376 |
+
const min=Math.min(...vals),max=Math.max(...vals),pad=(max-min||1)*.08,ymin=min-pad,ymax=max+pad;
|
| 377 |
+
const X=i=>p.l+(W-p.l-p.r)*(i/Math.max(1,vals.length-1));
|
| 378 |
+
const Y=v=>p.t+(H-p.t-p.b)*(1-(v-ymin)/(ymax-ymin));
|
| 379 |
+
ctx.strokeStyle="rgba(157,181,194,.16)";ctx.lineWidth=1;ctx.font="11px system-ui";ctx.fillStyle="#8ca6b5";
|
| 380 |
+
for(let j=0;j<=4;j++){
|
| 381 |
+
const y=p.t+(H-p.t-p.b)*j/4;
|
| 382 |
+
ctx.beginPath();ctx.moveTo(p.l,y);ctx.lineTo(W-p.r,y);ctx.stroke();
|
| 383 |
+
const val=ymax-(ymax-ymin)*j/4;ctx.fillText(fmt(val),6,y+4);
|
| 384 |
+
}
|
| 385 |
+
function line(data,color,width){
|
| 386 |
+
ctx.strokeStyle=color;ctx.lineWidth=width;ctx.beginPath();
|
| 387 |
+
data.forEach((v,i)=>{const x=X(i),y=Y(v);i?ctx.lineTo(x,y):ctx.moveTo(x,y)});ctx.stroke();
|
| 388 |
+
}
|
| 389 |
+
line(vals,"#35d7e7",1.7); line(smooth,"#ffd166",1.5);
|
| 390 |
+
ctx.fillStyle="#ff7e8a";
|
| 391 |
+
flags.forEach((f,i)=>{if(f.is){ctx.beginPath();ctx.arc(X(i),Y(vals[i]),4.3,0,Math.PI*2);ctx.fill()}});
|
| 392 |
+
ctx.fillStyle="#8ca6b5";
|
| 393 |
+
const ticks=Math.min(5,vals.length);
|
| 394 |
+
for(let j=0;j<ticks;j++){
|
| 395 |
+
const i=Math.round((vals.length-1)*j/Math.max(1,ticks-1)),txt=String(currentLabels[i]??i+1);
|
| 396 |
+
const short=txt.length>18?txt.slice(0,17)+"…":txt;
|
| 397 |
+
ctx.fillText(short,X(i)-Math.min(45,short.length*3),H-10);
|
| 398 |
+
}
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
function downloadAnomalies(){
|
| 402 |
+
if(!anomalies.length){alert("Keine Anomalien zum Exportieren.");return}
|
| 403 |
+
const x=$("xColumn").value,y=$("yColumn").value;
|
| 404 |
+
let csv=`${x==="__index__"?"index":x},${y},robust_score\n`;
|
| 405 |
+
anomalies.forEach(a=>csv+=`"${String(a.label).replace(/"/g,'""')}",${a.value},${a.score}\n`);
|
| 406 |
+
const blob=new Blob([csv],{type:"text/csv;charset=utf-8"});
|
| 407 |
+
const url=URL.createObjectURL(blob),a=document.createElement("a");
|
| 408 |
+
a.href=url;a.download="sensorik-anomalien.csv";a.click();URL.revokeObjectURL(url);
|
| 409 |
+
}
|
| 410 |
+
window.addEventListener("resize",()=>{if(currentValues.length) analyze()});
|
| 411 |
+
</script>
|
| 412 |
+
</body>
|
| 413 |
+
</html>
|
sensorik.png
ADDED
|
Git LFS Details
|