testsAndMisc/linux_configuration/misc/tools/_transcribe_cluster.py
Krzysztof kuhy Rudnicki 08aac8d7d1 Flatten i3-configuration and misc/testsAndMisc-bash
i3 config was filed under periodic_background, which it never was. It is
now linux_configuration/i3/ (config + installer) and
linux_configuration/i3blocks/ (the status scripts), both at depth 2.

misc/testsAndMisc-bash/ was a directory named after the repo it already
lived in, so its contents moved up to misc/. Its stray .gitignore came
along, keeping the media rules working from the new location; its
.vscode/tasks.json did not, because it invoked Bash/transcribe.sh, a path
this repo dropped long ago.

Most of this commit is the references the previous flatten missed. Its
regex skipped anything with a variable prefix, which left 16 files --
including check_and_enable_services.sh and install_core_system.sh, the
orchestrators -- still naming $CONFIG_DIR/scripts/periodic_background/...
It had also rewritten three i3 paths into scripts/i3blocks/, a directory
that has never existed; the i3blocks suites caught that by failing.

13 more symlinks broke on the i3 move. Repointed, and cpu_monitor/volume/
disk were run again to confirm the bar still reports.

Depth 139 -> 104. Roughly 47 of the remainder sit in digital_wellbeing
and hosts, still deferred. The rest encode real structure -- a C project,
Python package directories, artgate's routes/styles, systemd unit dirs --
so flattening them further would destroy information rather than
taxonomy.

Verified: 1798 tests at 100%, both i3blocks suites pass, the shell
coverage gate is clean, and the relocated media rules still ignore what
they should while test_fw.srt stays tracked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp
2026-08-23 19:02:18 +02:00

105 lines
3.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Resampling and cosine k-means, the maths behind speaker labelling.
numpy is fetched through ``_try_import`` rather than imported at module
scope, so this file remains importable on a machine without it - which is
what lets the transcribe tools be unit-tested in CI.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from _transcribe_media import _try_import
if TYPE_CHECKING:
import types
import numpy as np
import numpy.typing as npt
_NDIM_2D = 2
def _resample_linear(
x: npt.NDArray[np.float32],
src_sr: int,
tgt_sr: int,
) -> npt.NDArray[np.float32]:
"""Linearly resample 1-D audio array."""
np_mod = _try_import("numpy")
if np_mod is None:
msg = "numpy is required for resampling"
raise RuntimeError(msg)
if src_sr == tgt_sr:
return x
ratio = float(tgt_sr) / float(src_sr)
n_out = max(1, round(x.shape[-1] * ratio))
xp = np_mod.linspace(0.0, 1.0, num=x.shape[-1], endpoint=False)
xq = np_mod.linspace(0.0, 1.0, num=n_out, endpoint=False)
y = np_mod.interp(xq, xp, x.astype(np_mod.float32))
return y.astype(np_mod.float32)
def _kmeans_cosine(
embs: list[Any],
k: int,
iters: int = 50,
seed: int = 0,
) -> npt.NDArray[np.int64]:
"""Cluster embeddings with cosine-similarity k-means."""
np_mod = _try_import("numpy")
if np_mod is None:
msg = "numpy is required for clustering"
raise RuntimeError(msg)
rng = np_mod.random.default_rng(seed)
features = np_mod.asarray(embs, dtype=np_mod.float32)
if features.ndim != _NDIM_2D or features.shape[0] == 0:
return np_mod.zeros((0,), dtype=np_mod.int64)
features = features / (np_mod.linalg.norm(features, axis=1, keepdims=True) + 1e-8)
idxs = rng.choice(
features.shape[0],
size=min(k, features.shape[0]),
replace=False,
)
centroids = features[idxs]
if centroids.shape[0] < k:
pad = rng.standard_normal(
size=(
k - centroids.shape[0],
features.shape[1],
)
).astype(np_mod.float32)
pad /= np_mod.linalg.norm(pad, axis=1, keepdims=True) + 1e-8
centroids = np_mod.concatenate([centroids, pad], axis=0)
return _run_kmeans_iterations(np_mod, features, centroids, k, iters)
def _run_kmeans_iterations(
np_mod: types.ModuleType,
features: npt.NDArray[np.float32],
centroids: npt.NDArray[np.float32],
k: int,
iters: int,
) -> npt.NDArray[np.intp]:
"""Run k-means iteration loop and return labels."""
labels: Any = None
for _ in range(iters):
sims = features @ centroids.T
labels = sims.argmax(axis=1)
new_c = np_mod.zeros_like(centroids)
for j in range(k):
sel = features[labels == j]
if sel.shape[0] == 0:
new_c[j] = centroids[j]
else:
v = sel.mean(axis=0)
v /= np_mod.linalg.norm(v) + 1e-8
new_c[j] = v
if np_mod.allclose(new_c, centroids, atol=1e-4):
break
centroids = new_c
return labels