File size: 4,186 Bytes
63dd946
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Copyright (c) 2026 The Anuvani-Diarization authors
# SPDX-License-Identifier: MIT OR Apache-2.0
"""Trace pyannote's own front-end batching (`Inference.slide`, `SpeakerDiarization.get_embeddings`) with both
networks replaced by recorders, on CPU, and print the batches as JSON. Used by tests/test_front.py.

The waveform carries its own sample index (sample k has value k + 1, exact in float32 below 2^24), so each
recorded window row tells which samples it holds; zero padding stays 0.

  python -s tests/ref_front_trace.py LENGTHS_JSON MASK_SEED OUT_JSON
"""
import json
import os
import sys
from pathlib import Path

# never touch a GPU from the tests
os.environ["CUDA_VISIBLE_DEVICES"] = ""

import numpy as np

sys.path.insert(0, str(Path(__file__).resolve().parent))
import refpkg                                               # noqa: E402

refpkg.load()
import torch                                                # noqa: E402
from pyannote.core import SlidingWindowFeature              # noqa: E402
from anuvani_diar_ref.frontend import load_bundle             # noqa: E402

REL = Path(__file__).resolve().parents[2] / "anuvani_diar_v1"


def describe(rows):
    """For each (1, WIN) row: first sample index, number of non-padding samples, padding all zero."""
    out = []
    for r in rows:
        r = r.reshape(-1)
        nz = np.flatnonzero(r)
        valid = int(nz[-1]) + 1 if len(nz) else 0
        first = int(r[0]) - 1 if valid else -1
        ok = bool(valid == 0 or np.array_equal(r[:valid], np.arange(first + 1, first + 1 + valid, dtype=np.float32)))
        out.append([first, valid, ok and not r[valid:].any()])
    return out


def main():
    lengths = json.loads(sys.argv[1])
    rng = np.random.default_rng(int(sys.argv[2]))
    model = load_bundle(REL / "bundle", "cpu", 8, 2)
    pipe = model["pipeline"]
    # the real voiceprint model, on CPU
    real_min = pipe._embedding.min_num_samples
    seg_rec, emb_rec = [], []

    def fake_infer(chunks, soft=False):
        x = chunks.numpy()
        seg_rec.append(dict(B=int(x.shape[0]), rows=describe(x)))
        return np.zeros((x.shape[0], 799, 16), np.float32)

    class FakeEmbedding:
        sample_rate = 16000
        min_num_samples = real_min
        dimension = 256
        metric = "cosine"

        def __call__(self, waveforms, masks=None):
            emb_rec.append(dict(B=int(waveforms.shape[0]), rows=describe(waveforms.numpy()),
                                masks=masks.numpy().astype(np.uint8).tolist()))
            return np.zeros((waveforms.shape[0], 256), np.float32)

    pipe._segmentation.infer = fake_infer
    pipe._embedding = FakeEmbedding()
    result = dict(min_num_samples=int(real_min), receptive_field=[model["frames"].start, model["frames"].duration,
                                                                  model["frames"].step],
                  mapping=model["mapping"].tolist(), runs=[])
    for n in lengths:
        assert n < 2 ** 24
        audio = np.arange(1, n + 1, dtype=np.float32)
        file = {"waveform": torch.from_numpy(audio).unsqueeze(0), "sample_rate": 16000}
        seg_rec.clear(); emb_rec.clear()
        seg = pipe.get_segmentations(file, soft=False)
        c = seg.data.shape[0]
        density = rng.choice([0.05, 0.3, 0.6, 0.95])
        masks = (rng.random((c, 799, 4)) < density).astype(np.float32)
        # exact clean-frame counts at the threshold
        if c > 1:
            masks[1] = 0
            # 2 clean frames: falls back to the full mask
            masks[1, :2, 0] = 1
            # 3 clean frames: uses the clean mask
            masks[1, 10:13, 1] = 1
            # overlapped frames
            masks[1, 13:20, 1:3] = 1
        sw = seg.sliding_window
        emb = pipe.get_embeddings(file, SlidingWindowFeature(masks, sw), exclude_overlap=True)
        result["runs"].append(dict(n=n, chunks=c, sw=[sw.start, sw.duration, sw.step], seg=list(seg_rec),
                                   emb=list(emb_rec), masks=masks.astype(np.uint8).tolist(),
                                   emb_shape=list(emb.shape)))
    Path(sys.argv[3]).write_text(json.dumps(result))


if __name__ == "__main__":
    main()