File size: 12,373 Bytes
54183d5 f110a8c 54183d5 f110a8c 54183d5 f110a8c 54183d5 f110a8c 54183d5 f110a8c c1ea798 f110a8c 54183d5 f110a8c 1ca5c15 f110a8c 54183d5 1ca5c15 54183d5 f110a8c 54183d5 f110a8c 54183d5 f110a8c 54183d5 f110a8c 54183d5 f110a8c 54183d5 f110a8c 54183d5 f110a8c 54183d5 f110a8c 1ca5c15 f110a8c 54183d5 f110a8c 54183d5 b3e10d5 54183d5 f110a8c 54183d5 f110a8c 54183d5 f110a8c 54183d5 f110a8c 54183d5 f110a8c 54183d5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 | """Static data for the About tab β algorithms, metrics, datasets.
Kept separate from app.py for clarity and maintainability.
Algorithms are ordered: CPD/Distribution-based, CPD/Subsequence-based,
SD/Integrated, SD/Decomposed.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Sequence
# ---------------------------------------------------------------------------
# Algorithms
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class AlgoEntry:
"""One row of the algorithm table."""
name: str
task: str # "CPD" or "SD"
family: str # Distribution-based, Subsequence-based, Integrated, Decomposed
complexity: str # Big-O complexity (math notation, no $ wrappers)
license: str # SPDX-like short id, or "β" if unknown
source: str # upstream project / author
# Ordered: CPD Distribution-based β CPD Subsequence-based β SD Integrated β SD Decomposed
ALGO_ENTRIES: Sequence[AlgoEntry] = (
# ββ CPD Β· Distribution-based ββββββββββββββββββββββββββββββββββββββββββ
AlgoEntry("AMOC", "CPD", "Distribution-based", "O(dn)", "BSD-2", "ruptures"),
AlgoEntry("BinSeg", "CPD", "Distribution-based", "O(dn\\log n)", "BSD-2", "ruptures"),
AlgoEntry("BottomUp", "CPD", "Distribution-based", "O(dn)", "BSD-2", "ruptures"),
AlgoEntry("BOCD", "CPD", "Distribution-based", "O(dn^2)", "Apache-2.0", "hildensia"),
AlgoEntry("DynP", "CPD", "Distribution-based", "O(n_{cp}\\,dn^2)", "BSD-2", "ruptures"),
AlgoEntry("EAgglo", "CPD", "Distribution-based", "O(dn^2)", "BSD-3", "aeon"),
AlgoEntry("GreedyGaussian", "CPD", "Distribution-based", "O(n_{cp}^2 d^3 n)", "BSD-3", "aeon"),
AlgoEntry("ICID", "CPD", "Distribution-based", "O(dn)", "GPLv3", "IsolationKernel"),
AlgoEntry("KCPD", "CPD", "Distribution-based", "O(n_{cp}\\,dn^2)", "BSD-2", "ruptures"),
AlgoEntry("Pelt", "CPD", "Distribution-based", "O(dn)", "BSD-2", "ruptures"),
AlgoEntry("Prophet", "CPD", "Distribution-based", "O(n_{cp}\\,dn)", "MIT", "Facebook"),
AlgoEntry("Window", "CPD", "Distribution-based", "O(dn)", "BSD-2", "ruptures"),
AlgoEntry("Beast", "CPD", "Distribution-based", "-", "MIT", "Rbeast"),
# ββ CPD Β· Subsequence-based βββββββββββββββββββββββββββββββββββββββββββ
AlgoEntry("CLaSP", "CPD", "Subsequence-based", "O(n_{cp}\\,dn^2)", "BSD-3", "claspy"),
AlgoEntry("ESPRESSO", "CPD", "Subsequence-based", "O(dn^2 + n_{cp}\\,n)", "β", "cruiseresearchgroup"),
AlgoEntry("FLUSS", "CPD", "Subsequence-based", "O(dn^2 + n_{cp}\\,n)", "BSD-3", "aeon"),
AlgoEntry("InformationGain", "CPD", "Subsequence-based", "O(n_{cp}\\,dn^2)", "BSD-3", "aeon"),
AlgoEntry("TGLAD", "CPD", "Subsequence-based", "O(d^3 n)", "Non-Commercial", "Harshs27"),
AlgoEntry("Tire", "CPD", "Subsequence-based", "O(dn)", "β", "KU Leuven"),
AlgoEntry("TSCP2", "CPD", "Subsequence-based", "O(dn)", "β", "cruiseresearchgroup"),
# ββ SD Β· Integrated βββββββββββββββββββββββββββββββββββββββββββββββββββ
AlgoEntry("AutoPlait", "SD", "Integrated", "O(dn)", "β", "β"),
AlgoEntry("HdpHsmm", "SD", "Integrated", "O(dn + n_s \\min(n, 2000)^2)", "β", "mattjj/pyhsmm (reimpl.)"),
AlgoEntry("Hidalgo", "SD", "Integrated", "O(dn^2)", "BSD-3", "aeon"),
AlgoEntry("HMM", "SD", "Integrated", "-", "BSD-3", "hmmlearn"),
# ββ SD Β· Decomposed βββββββββββββββββββββββββββββββββββββββββββββββββββ
AlgoEntry("TICC", "SD", "Decomposed", "O(n_s(dn + (dl)^3))", "BSD-2", "davidhallac"),
AlgoEntry("Time2State", "SD", "Decomposed", "O(dn)", "MIT", "Lab-ANT"),
AlgoEntry("E2USD", "SD", "Decomposed", "O(dn\\log l)", "β", "AI4CTS"),
AlgoEntry("Clap", "SD", "Decomposed", "O(n_{cp}\\,n(nd + n_{cp}\\log n_{cp}))", "BSD-3", "claspy"),
)
# Quick lookup by name (case-insensitive)
ALGO_BY_NAME: dict[str, AlgoEntry] = {a.name.lower(): a for a in ALGO_ENTRIES}
def get_algo_entry(name: str) -> AlgoEntry | None:
"""Lookup an algorithm entry by name (case-insensitive)."""
return ALGO_BY_NAME.get(name.lower())
# ---------------------------------------------------------------------------
# Complexity classification (for color-coding in the UI)
# ---------------------------------------------------------------------------
def classify_complexity(complexity: str) -> str:
"""Categorize a complexity string into a speed tier.
Returns one of: ``linear``, ``log_linear``, ``quadratic``, ``cubic``.
"""
s = complexity.lower().replace(" ", "")
if "n^3" in s or "nΒ³" in s:
return "cubic"
if "n^2" in s or "nΒ²" in s:
return "quadratic"
if "logn" in s or "log(n)" in s or "\\logn" in s:
return "log_linear"
return "linear"
COMPLEXITY_COLORS = {
"linear": "#1e7d32", # green
"log_linear": "#1565c0", # blue
"quadratic": "#ef6c00", # orange
"cubic": "#c62828", # red
}
COMPLEXITY_LABELS = {
"linear": "linear",
"log_linear": "log-linear",
"quadratic": "quadratic",
"cubic": "cubic",
}
# ---------------------------------------------------------------------------
# Variable glossary (for tooltips)
# ---------------------------------------------------------------------------
COMPLEXITY_GLOSSARY = {
"n": "number of points in the time series",
"d": "number of dimensions (channels)",
"l": "window size",
"n_cp": "number of change points",
"n_s": "number of distinct states",
}
COMPLEXITY_GLOSSARY_MD = "**Complexity notation:** " + " Β· ".join(
f"`{k}` = {v}" for k, v in COMPLEXITY_GLOSSARY.items()
)
def format_complexity_plain(s: str) -> str:
"""Render a LaTeX-ish complexity string as plain text.
The complexity strings in :data:`ALGO_ENTRIES` use LaTeX fragments such as
``\\log``, ``n^2`` and ``n_{cp}``. Gradio's Markdown renderer on the
Hugging Face Space runtime does not consistently process ``$...$`` math,
so we convert these strings into a readable Unicode form (e.g.
``O(dn log l)``, ``O(n_cp dnΒ²)``) instead of relying on KaTeX.
"""
import re
if not s:
return s
out = s
out = out.replace("\\log", " log").replace("\\cdot", "Β·").replace("\\,", " ")
out = (
out.replace("n^2", "nΒ²")
.replace("n^3", "nΒ³")
.replace("d^2", "dΒ²")
.replace("d^3", "dΒ³")
.replace("n_s^2", "n_sΒ²")
.replace("n_{cp}^2", "n_cpΒ²")
)
# Subscripts: n_{cp} -> n_cp
out = re.sub(r"_\{([^}]+)\}", r"_\1", out)
# Strip any leftover backslashes
out = out.replace("\\", "")
return out
# ---------------------------------------------------------------------------
# Metrics
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class MetricEntry:
name: str
task: str # "CPD" or "SD"
description: str
METRIC_ENTRIES: Sequence[MetricEntry] = (
# CPD
MetricEntry(
"F1 Score", "CPD", "Classic precision / recall / F1 with a hard margin tolerance around each true change point"
),
MetricEntry(
"Gaussian F1",
"CPD",
"Gaussian-weighted soft F1 β predictions are rewarded via a Gaussian kernel "
"that decays with distance, no hard margin",
),
MetricEntry("Covering", "CPD", "Segment-wise Intersection over Union (IoU) weighted by segment duration"),
MetricEntry(
"Bidirectional Covering",
"CPD",
"Evaluates coverage in both directions (truthβpred and predβtruth) aggregated by harmonic mean",
),
MetricEntry(
"Hausdorff Distance",
"CPD",
"Maximum over all true CPs of the distance to the nearest predicted CP (and vice-versa)",
),
# SD
MetricEntry(
"Adjusted Rand Index", "SD", "Chance-adjusted agreement between true and predicted label partitions (sklearn)"
),
MetricEntry(
"Normalized Mutual Information", "SD", "Information-theoretic agreement normalised to [0, 1] (sklearn)"
),
MetricEntry("Adjusted Mutual Information", "SD", "Chance-adjusted variant of NMI (sklearn)"),
MetricEntry("Weighted ARI", "SD", "ARI variant that gives more weight to samples near segment boundaries"),
MetricEntry("Weighted NMI", "SD", "NMI variant that gives more weight to samples near segment boundaries"),
MetricEntry(
"State Matching Score",
"SD",
"Hungarian matching of predicted to true states with fine-grained error classification",
),
)
# ---------------------------------------------------------------------------
# Datasets
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class DatasetEntry:
name: str
dtype: str # "Generated" or "Real-world"
description: str
dimensions: str
segments: str
source: str
DATASET_ENTRIES: Sequence[DatasetEntry] = (
DatasetEntry(
"Sample dataset",
"Real-world",
"A single labelled trial from CMU MoCap 86 (humeral & femoral angles), served as the built-in example",
"4",
"3β8",
"[CMU MoCap](https://mocap.cs.cmu.edu/)",
),
DatasetEntry(
"Synthetic",
"Generated",
"Configurable generator with 7 intra-segment patterns "
"(Gaussian processes, sinusoidal, constant, step, stairs, linear, mixed), noise control, recurring states",
"1β8",
"2β12",
"tsseg",
),
)
# ---------------------------------------------------------------------------
# Markdown builders
# ---------------------------------------------------------------------------
def build_algo_table(runtime_detectors: dict | None = None) -> str:
"""Return a Markdown table of algorithms (no group separator rows)."""
rows: list[str] = []
for a in ALGO_ENTRIES:
complexity = format_complexity_plain(a.complexity)
rows.append(f"| {a.name} | {a.task} | {a.family} | `{complexity}` | {a.license} | {a.source} |")
header = (
"| Algorithm | Task | Family | Complexity | License | Source |\n"
"|-----------|------|--------|------------|---------|--------|\n"
)
return header + "\n".join(rows)
def build_metric_table() -> str:
rows: list[str] = []
for m in METRIC_ENTRIES:
rows.append(f"| {m.name} | {m.task} | {m.description} |")
header = "| Metric | Task | Description |\n|--------|------|-------------|\n"
return header + "\n".join(rows)
def build_dataset_table() -> str:
rows: list[str] = []
for d in DATASET_ENTRIES:
rows.append(f"| {d.name} | {d.dtype} | {d.description} | {d.dimensions} | {d.segments} | {d.source} |")
header = (
"| Dataset | Type | Description | Dimensions | Segments | Source |\n"
"|---------|------|-------------|------------|----------|--------|\n"
)
return header + "\n".join(rows)
DISCLAIMER_MD = (
"> **β οΈ Disclaimer:** Some algorithms were adapted from research paper artifacts "
'published without an explicit license (marked "β" above). These are provided '
"for **research and educational purposes only**. If you are the author of one of "
"these works and wish to specify licensing terms, please "
"[open an issue](https://github.com/fchavelli/tsseg/issues)."
)
MOCAP_NOTE_MD = (
"> The sample dataset uses data from the **CMU Graphics Lab Motion Capture Database** "
"(NSF EIA-0196217). Labels were sourced from Time2State (Wang et al., 2023). "
"Only the first rotation axis of left/right humerus and femur is kept, following "
"the protocol of AutoPlait (Matsubara et al., 2014)."
)
LINKS_MD = """- [tsseg Documentation](https://fchavelli.github.io/tsseg/)
- [GitHub Repository](https://github.com/fchavelli/tsseg)
- [License: AGPLv3](https://github.com/fchavelli/tsseg/blob/main/LICENSE)
Built with [Gradio](https://gradio.app) Β· Powered by [tsseg](https://github.com/fchavelli/tsseg)"""
|