GokseninYuksel commited on
Commit
356f73e
·
verified ·
1 Parent(s): 82e1e28

Upload Bearings checkpoint

Browse files
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- library_name: pytorch
3
  tags:
4
  - audio
5
  - spatial-audio
@@ -22,7 +22,25 @@ The encoder needs its tokens fused with a content encoder such as [GRAM-T](https
22
 
23
  ## Usage
24
 
25
- Clone the repository and install `requirements.txt`, then:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  ```python
28
  import torch
@@ -37,12 +55,10 @@ out["timestamps"].shape # (600,) seconds, one per token, window centres: 0.04
37
  out["n_valid_time"] # 67: time steps that end before the zero padding
38
  ```
39
 
40
- `encode_waveform` resamples to 32 kHz, applies per-clip W-RMS normalisation, zero-pads to a whole number of 2 s windows, encodes each window and concatenates along time.
41
 
42
  **The token axis stacks the T x F grid time-major:** token `i` is time step `i // 8` and mel band `i % 8`. The 8 bands of a time step are adjacent and share a timestamp. Each time step covers 80 ms. `embeddings.view(B, n_time, n_freq, E)` restores the grid and `.reshape(B, n_time, 8 * 384)` gives the per-frame vectors the SELD probes consume.
43
 
44
  ## Evaluation
45
 
46
  Used as a frozen spatial stream next to a frozen content encoder in SELD probes on TAU-NIGENS 2021 and STARSS23 (DCASE 2023 metrics: ER20, F20, LE, LR). Scripts and task ids are in `seld-dcase2023-gram/` of the repository.
47
-
48
-
 
1
  ---
2
+ library_name: transformers
3
  tags:
4
  - audio
5
  - spatial-audio
 
22
 
23
  ## Usage
24
 
25
+ With transformers (no clone needed, `pip install transformers torchaudio`):
26
+
27
+ ```python
28
+ import torch
29
+ from transformers import AutoModel
30
+
31
+ model = AutoModel.from_pretrained("labhamlet/bearings", trust_remote_code=True).eval()
32
+ wav = torch.randn(1, 4, int(48000 * 5.3)) # (batch, [W, Y, Z, X], samples), any sample rate
33
+
34
+ with torch.no_grad():
35
+ out = model(wav, sampling_rate=48000)
36
+ out.last_hidden_state.shape # (1, 600, 384): B x (T * F) x E, T = 75 time steps, F = 8 mel bands
37
+ out.timestamps.shape # (600,) seconds, one per token, window centres: 0.04, ..., 5.96
38
+ out.n_valid_time # tensor([67]): time steps that end before the zero padding
39
+ ```
40
+
41
+ `strategy="frames"` returns (B, T, 8 * 384) per-frame vectors, `strategy="mean"` and `strategy="cls"` return one (B, 384) vector per clip. `AutoFeatureExtractor.from_pretrained("labhamlet/bearings", trust_remote_code=True)` does the resampling and padding separately if you want to cache waveforms.
42
+
43
+ With the research code, clone the repository and install `requirements.txt`, then:
44
 
45
  ```python
46
  import torch
 
55
  out["n_valid_time"] # 67: time steps that end before the zero padding
56
  ```
57
 
58
+ `encode_waveform` resamples to 32 kHz, applies per-clip W-RMS normalisation, zero-pads to a whole number of 2 s windows, encodes each window and concatenates along time. The AutoModel forward does exactly the same and the two paths give identical embeddings.
59
 
60
  **The token axis stacks the T x F grid time-major:** token `i` is time step `i // 8` and mel band `i % 8`. The 8 bands of a time step are adjacent and share a timestamp. Each time step covers 80 ms. `embeddings.view(B, n_time, n_freq, E)` restores the grid and `.reshape(B, n_time, 8 * 384)` gives the per-frame vectors the SELD probes consume.
61
 
62
  ## Evaluation
63
 
64
  Used as a frozen spatial stream next to a frozen content encoder in SELD probes on TAU-NIGENS 2021 and STARSS23 (DCASE 2023 metrics: ER20, F20, LE, LR). Scripts and task ids are in `seld-dcase2023-gram/` of the repository.
 
 
config.json CHANGED
@@ -1,57 +1,29 @@
1
  {
2
- "encoder_embedding_dim": 384,
 
 
 
 
 
 
 
 
3
  "encoder_depth": 6,
4
- "num_heads": 12,
5
- "mlp_ratio": 4.0,
6
- "decoder_depth": 4,
7
- "decoder_num_heads": 8,
8
- "decoder_embedding_dim": 256,
9
- "q_loss_weight": 1.0,
10
- "diffuseness_loss_weight": 1.0,
11
- "leveldiff_loss_weight": 0.0,
12
- "level_loss_weight": 0.0,
13
- "lr": 0.0002,
14
- "b1": 0.9,
15
- "b2": 0.98,
16
- "weight_decay": 0.01,
17
- "warmup_steps": 10000,
18
  "in_channels": 7,
19
- "sr": 32000,
 
 
 
 
20
  "num_mel_bins": 128,
21
- "input_length": 500,
22
  "target_length": 200,
23
- "inter_channel_heads": 4,
24
- "inter_channel_layers": 1,
25
- "gramt_model_id": "labhamlet/gramt-mono",
26
- "gramt_mask_context": false,
27
- "n_grid": 256,
28
- "vmf_kappa": 40.0,
29
- "grid_chunk": 64,
30
- "coh_tile_t": 5,
31
- "coh_tile_f": 3,
32
- "coh_long_t": 35,
33
- "coh_long_f": 3,
34
- "diffuseness_c": 1.0,
35
- "mask_ratio": 0.8,
36
- "mask_p_span": 0.4,
37
- "mask_p_random": 0.2,
38
- "mask_p_censor": 0.4,
39
- "span_min_tokens": 2,
40
- "span_max_tokens": null,
41
- "rotation_mode": "so3",
42
- "rotation_prob": 1.0,
43
- "f_max": 14000,
44
- "log_every_n_steps": 500,
45
- "samples_per_clip": 2,
46
- "native_sr": 48000,
47
- "model_class": "src.model.SphereV5",
48
- "fshape": 16,
49
  "tshape": 8,
50
- "fstride": 16,
51
- "tstride": 8,
52
- "global_step": 100000,
53
- "source_checkpoint": "step=100000.ckpt",
54
- "stripped_prefixes": [
55
- "gram."
56
- ]
57
- }
 
1
  {
2
+ "architectures": [
3
+ "BearingsModel"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration_bearings.BearingsConfig",
7
+ "AutoFeatureExtractor": "feature_extraction_bearings.BearingsFeatureExtractor",
8
+ "AutoModel": "modeling_bearings.BearingsModel"
9
+ },
10
+ "dtype": "float32",
11
  "encoder_depth": 6,
12
+ "encoder_embedding_dim": 384,
13
+ "f_max": 14000.0,
14
+ "f_min": 50.0,
15
+ "fshape": 16,
16
+ "hop_length": 320,
 
 
 
 
 
 
 
 
 
17
  "in_channels": 7,
18
+ "layer_norm_eps": 1e-05,
19
+ "mlp_ratio": 4.0,
20
+ "model_type": "bearings",
21
+ "n_fft": 1024,
22
+ "num_heads": 12,
23
  "num_mel_bins": 128,
24
+ "sampling_rate": 32000,
25
  "target_length": 200,
26
+ "transformers_version": "4.57.6",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  "tshape": 8,
28
+ "win_length": 1024
29
+ }
 
 
 
 
 
 
configuration_bearings.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Bearings configuration for the transformers AutoModel interface."""
2
+ from transformers import PretrainedConfig
3
+
4
+
5
+ class BearingsConfig(PretrainedConfig):
6
+ model_type = "bearings"
7
+
8
+ def __init__(
9
+ self,
10
+ encoder_embedding_dim: int = 384,
11
+ encoder_depth: int = 6,
12
+ num_heads: int = 12,
13
+ mlp_ratio: float = 4.0,
14
+ layer_norm_eps: float = 1e-5,
15
+ in_channels: int = 7,
16
+ num_mel_bins: int = 128,
17
+ target_length: int = 200,
18
+ fshape: int = 16,
19
+ tshape: int = 8,
20
+ sampling_rate: int = 32000,
21
+ n_fft: int = 1024,
22
+ win_length: int = 1024,
23
+ hop_length: int = 320,
24
+ f_min: float = 50.0,
25
+ f_max: float = 14000.0,
26
+ **kwargs,
27
+ ):
28
+ self.encoder_embedding_dim = encoder_embedding_dim
29
+ self.encoder_depth = encoder_depth
30
+ self.num_heads = num_heads
31
+ self.mlp_ratio = mlp_ratio
32
+ self.layer_norm_eps = layer_norm_eps
33
+ self.in_channels = in_channels
34
+ self.num_mel_bins = num_mel_bins
35
+ self.target_length = target_length
36
+ self.fshape = fshape
37
+ self.tshape = tshape
38
+ self.sampling_rate = sampling_rate
39
+ self.n_fft = n_fft
40
+ self.win_length = win_length
41
+ self.hop_length = hop_length
42
+ self.f_min = f_min
43
+ self.f_max = f_max
44
+ super().__init__(**kwargs)
45
+
46
+ @property
47
+ def n_freq_patches(self) -> int:
48
+ return self.num_mel_bins // self.fshape
49
+
50
+ @property
51
+ def n_time_patches(self) -> int:
52
+ return self.target_length // self.tshape
feature_extraction_bearings.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Waveform-level feature extractor for Bearings.
2
+
3
+ Resamples first-order Ambisonic audio to the model rate and zero-pads it to a
4
+ whole number of 2 s windows. Log-mel / intensity-vector extraction and the
5
+ per-clip W-RMS normalisation live inside the model so that the model can also
6
+ be called on raw waveforms directly.
7
+ """
8
+ import math
9
+ from typing import List, Optional, Union
10
+
11
+ import numpy as np
12
+ import torch
13
+ from transformers.feature_extraction_utils import BatchFeature, FeatureExtractionMixin
14
+
15
+
16
+ class BearingsFeatureExtractor(FeatureExtractionMixin):
17
+ model_input_names = ["input_values"]
18
+
19
+ def __init__(self, sampling_rate: int = 32000, window_seconds: float = 2.0,
20
+ **kwargs):
21
+ super().__init__(**kwargs)
22
+ self.sampling_rate = sampling_rate
23
+ self.window_seconds = window_seconds
24
+
25
+ def __call__(self, audio: Union[np.ndarray, torch.Tensor, List],
26
+ sampling_rate: Optional[int] = None,
27
+ return_tensors: str = "pt") -> BatchFeature:
28
+ """audio: (4, N) or (B, 4, N), channels W, Y, Z, X (ACN/SN3D)."""
29
+ if sampling_rate is None:
30
+ raise ValueError("pass sampling_rate= of the audio you are feeding")
31
+ wav = torch.as_tensor(np.asarray(audio) if not torch.is_tensor(audio) else audio)
32
+ wav = wav.float()
33
+ if wav.ndim == 2:
34
+ wav = wav.unsqueeze(0)
35
+ if wav.ndim != 3 or wav.shape[1] != 4:
36
+ raise ValueError(f"expected (B, 4, N) FOA audio, got {tuple(wav.shape)}")
37
+ if int(sampling_rate) != self.sampling_rate:
38
+ import torchaudio
39
+ wav = torchaudio.functional.resample(
40
+ wav, int(sampling_rate), self.sampling_rate, lowpass_filter_width=64)
41
+ n_valid = wav.shape[-1]
42
+ win = int(self.window_seconds * self.sampling_rate)
43
+ n_win = max(1, math.ceil(n_valid / win))
44
+ wav = torch.nn.functional.pad(wav, (0, n_win * win - n_valid))
45
+ out = {"input_values": wav,
46
+ "n_valid_samples": torch.full((wav.shape[0],), n_valid, dtype=torch.long)}
47
+ if return_tensors == "np":
48
+ out = {k: v.numpy() for k, v in out.items()}
49
+ elif return_tensors != "pt":
50
+ raise ValueError("return_tensors must be 'pt' or 'np'")
51
+ return BatchFeature(out)
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4bc76bc5e4711ff5857f31d0cc4e89083110d232a9e58aee42f14935e8b22422
3
+ size 44552888
modeling_bearings.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Bearings: self-supervised sound-field encoder for first-order Ambisonics.
2
+
3
+ Standalone transformers port of the SphereV5 encoder
4
+ (https://github.com/root-goksenin/Embisonics, src/model/mae.py). Only the
5
+ inference path is here: log-mel + active-intensity front end, patch
6
+ tokeniser, ViT encoder. The pre-training decoder and targets are not.
7
+
8
+ model = AutoModel.from_pretrained("labhamlet/bearings", trust_remote_code=True)
9
+ out = model(wav, sampling_rate=48000) # wav: (B, 4, N) FOA, W Y Z X
10
+ out.last_hidden_state # (B, T * F, E) time-major stacked tokens
11
+ out.timestamps # (T * F,) seconds
12
+ """
13
+ import math
14
+ from dataclasses import dataclass
15
+ from typing import Optional
16
+
17
+ import torch
18
+ import torch.nn.functional as F
19
+ from torch import nn
20
+ from torchaudio.transforms import MelScale, Spectrogram
21
+ from transformers import PreTrainedModel
22
+ from transformers.utils import ModelOutput
23
+
24
+ from .configuration_bearings import BearingsConfig
25
+
26
+
27
+ # =============================================================================
28
+ # Front end: 4 log-mel + 3 normalised active-intensity channels
29
+ # =============================================================================
30
+
31
+ class FeatureExtractor(nn.Module):
32
+ """Input (B, 4, N) ACN/SN3D FOA -> (B, 7, M, T): log-mel of W, Y, Z, X
33
+ and the per-band energy-normalised active intensity vector."""
34
+
35
+ def __init__(self, sample_rate, n_fft, win_length, hop_length, f_min, f_max,
36
+ n_mels, power=2.0):
37
+ super().__init__()
38
+ self.power = power
39
+ self.eps = 1e-6
40
+ self.spectrogram = Spectrogram(
41
+ n_fft=n_fft, win_length=win_length, hop_length=hop_length,
42
+ window_fn=torch.hann_window, power=None, center=True,
43
+ pad_mode="reflect", onesided=True,
44
+ )
45
+ self.mel_scale = MelScale(n_mels, sample_rate, f_min, f_max,
46
+ n_fft // 2 + 1, None, "htk")
47
+
48
+ def _intensity_energy(self, spec):
49
+ W, YZX = spec[:, [0]], spec[:, 1:]
50
+ I = 2.0 * torch.real(torch.conj(W) * YZX) # (B, 3, F, T)
51
+ E = torch.abs(W) ** 2 + (torch.abs(YZX) ** 2).sum(1, keepdim=True)
52
+ return I, E
53
+
54
+ def forward(self, audio):
55
+ spec = self.spectrogram(audio) # complex (B, 4, F, T)
56
+ logmel = torch.log(self.mel_scale(torch.abs(spec) ** self.power) + self.eps)
57
+ I, E = self._intensity_energy(spec)
58
+ aiv = self.mel_scale(I) / (self.mel_scale(E) + self.eps) # (B, 3, M, T)
59
+ return torch.cat([logmel, aiv], dim=1) # (B, 7, M, T)
60
+
61
+
62
+ # =============================================================================
63
+ # ViT pieces (parameter names match timm's Block)
64
+ # =============================================================================
65
+
66
+ class Attention(nn.Module):
67
+ def __init__(self, dim, num_heads):
68
+ super().__init__()
69
+ self.num_heads = num_heads
70
+ self.head_dim = dim // num_heads
71
+ self.qkv = nn.Linear(dim, dim * 3, bias=True)
72
+ self.proj = nn.Linear(dim, dim)
73
+
74
+ def forward(self, x):
75
+ B, N, C = x.shape
76
+ qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, self.head_dim).permute(2, 0, 3, 1, 4)
77
+ q, k, v = qkv.unbind(0)
78
+ x = F.scaled_dot_product_attention(q, k, v)
79
+ return self.proj(x.transpose(1, 2).reshape(B, N, C))
80
+
81
+
82
+ class Mlp(nn.Module):
83
+ def __init__(self, dim, hidden):
84
+ super().__init__()
85
+ self.fc1 = nn.Linear(dim, hidden)
86
+ self.act = nn.GELU()
87
+ self.fc2 = nn.Linear(hidden, dim)
88
+
89
+ def forward(self, x):
90
+ return self.fc2(self.act(self.fc1(x)))
91
+
92
+
93
+ class Block(nn.Module):
94
+ def __init__(self, dim, num_heads, mlp_ratio, eps):
95
+ super().__init__()
96
+ self.norm1 = nn.LayerNorm(dim, eps=eps)
97
+ self.attn = Attention(dim, num_heads)
98
+ self.norm2 = nn.LayerNorm(dim, eps=eps)
99
+ self.mlp = Mlp(dim, int(dim * mlp_ratio))
100
+
101
+ def forward(self, x):
102
+ x = x + self.attn(self.norm1(x))
103
+ return x + self.mlp(self.norm2(x))
104
+
105
+
106
+ class PatchEmbed(nn.Module):
107
+ """Non-overlapping Conv2D tokeniser, (B, 7, F, T) -> (B, P, D), freq-major."""
108
+
109
+ def __init__(self, in_ch, dim, fshape, tshape):
110
+ super().__init__()
111
+ self.proj = nn.Conv2d(in_ch, dim, kernel_size=(fshape, tshape), stride=(fshape, tshape))
112
+
113
+ def forward(self, x):
114
+ x = self.proj(x)
115
+ B, D, Nf, Nt = x.shape
116
+ return x.permute(0, 2, 3, 1).reshape(B, Nf * Nt, D)
117
+
118
+
119
+ # =============================================================================
120
+ # Model
121
+ # =============================================================================
122
+
123
+ @dataclass
124
+ class BearingsOutput(ModelOutput):
125
+ """last_hidden_state: (B, T * F, E) for strategy 'timestamps'; (B, T, F * E)
126
+ for 'frames'; (B, E) for 'mean' / 'cls'. Token axis of 'timestamps' is
127
+ TIME-MAJOR: index = t * F + f (F = 8 mel bands per 80 ms time step)."""
128
+ last_hidden_state: torch.FloatTensor = None
129
+ timestamps: Optional[torch.FloatTensor] = None
130
+ n_time: Optional[int] = None
131
+ n_freq: Optional[int] = None
132
+ n_valid_time: Optional[torch.LongTensor] = None
133
+
134
+
135
+ class BearingsPreTrainedModel(PreTrainedModel):
136
+ config_class = BearingsConfig
137
+ base_model_prefix = "bearings"
138
+ main_input_name = "input_values"
139
+ supports_gradient_checkpointing = False
140
+
141
+ def _init_weights(self, module):
142
+ if isinstance(module, nn.Linear):
143
+ nn.init.xavier_uniform_(module.weight)
144
+ if module.bias is not None:
145
+ nn.init.zeros_(module.bias)
146
+
147
+
148
+ class BearingsModel(BearingsPreTrainedModel):
149
+ def __init__(self, config: BearingsConfig):
150
+ super().__init__(config)
151
+ c = config
152
+ D = c.encoder_embedding_dim
153
+ self.n_freq = c.n_freq_patches
154
+ self.n_time_per_window = c.n_time_patches
155
+ P = self.n_freq * self.n_time_per_window
156
+
157
+ self.melspec = FeatureExtractor(
158
+ c.sampling_rate, c.n_fft, c.win_length, c.hop_length, c.f_min, c.f_max,
159
+ c.num_mel_bins, power=2.0,
160
+ )
161
+ self.patch_embed = PatchEmbed(c.in_channels, D, c.fshape, c.tshape)
162
+ self.cls_token = nn.Parameter(torch.zeros(1, 1, D))
163
+ # fixed 2-D sincos embedding, shipped with the weights
164
+ self.register_buffer("pos_embed", torch.zeros(1, P + 1, D), persistent=True)
165
+ self.encoder_blocks = nn.ModuleList([
166
+ Block(D, c.num_heads, c.mlp_ratio, c.layer_norm_eps) for _ in range(c.encoder_depth)
167
+ ])
168
+ self.encoder_norm = nn.LayerNorm(D, eps=c.layer_norm_eps)
169
+ self.post_init()
170
+
171
+ # ---- pieces -----------------------------------------------------------
172
+ @property
173
+ def window_samples(self) -> int:
174
+ return self.config.target_length * self.config.hop_length
175
+
176
+ def extract_features(self, wav: torch.Tensor) -> torch.Tensor:
177
+ """(B, 4, N) at config.sampling_rate -> (B, 7, T, M), W-RMS normalised."""
178
+ with torch.autocast(device_type=wav.device.type, enabled=False):
179
+ wav = wav.float()
180
+ rms = torch.sqrt(wav[:, :1].pow(2).mean(dim=-1, keepdim=True) + 1e-8)
181
+ return self.melspec(wav / rms).transpose(-1, -2)
182
+
183
+ def encode_window(self, x7: torch.Tensor) -> torch.Tensor:
184
+ """(B, 7, 200, 128) -> (B, 1 + 200, E); tokens after CLS are freq-major."""
185
+ x = self.patch_embed(x7.transpose(2, 3)) + self.pos_embed[:, 1:]
186
+ cls = self.cls_token.expand(x.shape[0], -1, -1) + self.pos_embed[:, :1]
187
+ x = torch.cat([cls, x], dim=1)
188
+ for blk in self.encoder_blocks:
189
+ x = blk(x)
190
+ return self.encoder_norm(x)
191
+
192
+ # ---- forward ----------------------------------------------------------
193
+ def forward(self, input_values: torch.Tensor, sampling_rate: Optional[int] = None,
194
+ n_valid_samples: Optional[torch.Tensor] = None,
195
+ strategy: str = "timestamps", chunk_batch: int = 64,
196
+ return_dict: bool = True):
197
+ """input_values: (B, 4, N) or (4, N) FOA waveform, channels W, Y, Z, X.
198
+ sampling_rate: rate of input_values; resampled to config.sampling_rate
199
+ when different (omit it if you used BearingsFeatureExtractor).
200
+ strategy: 'timestamps' (B, T*F, E) + timestamps | 'frames' (B, T, F*E)
201
+ | 'mean' (B, E) | 'cls' (B, E)."""
202
+ c = self.config
203
+ wav = input_values
204
+ if wav.ndim == 2:
205
+ wav = wav.unsqueeze(0)
206
+ if wav.ndim != 3 or wav.shape[1] != 4:
207
+ raise ValueError(f"expected (B, 4, N) FOA audio, got {tuple(wav.shape)}")
208
+ wav = wav.to(self.pos_embed.device, torch.float32)
209
+ if sampling_rate is not None and int(sampling_rate) != c.sampling_rate:
210
+ import torchaudio
211
+ wav = torchaudio.functional.resample(
212
+ wav, int(sampling_rate), c.sampling_rate, lowpass_filter_width=64)
213
+
214
+ B, _, N = wav.shape
215
+ if n_valid_samples is None:
216
+ n_valid_samples = torch.full((B,), N, dtype=torch.long, device=wav.device)
217
+ win = self.window_samples
218
+ n_win = max(1, math.ceil(N / win))
219
+ wav = F.pad(wav, (0, n_win * win - N))
220
+
221
+ x7 = self.extract_features(wav)[:, :, :n_win * c.target_length] # (B, 7, T, M)
222
+ x7 = x7.reshape(B, 7, n_win, c.target_length, c.num_mel_bins)
223
+ x7 = x7.permute(0, 2, 1, 3, 4).reshape(B * n_win, 7, c.target_length, c.num_mel_bins)
224
+
225
+ zs = [self.encode_window(x7[i:i + chunk_batch]).float()
226
+ for i in range(0, x7.shape[0], chunk_batch)]
227
+ z = torch.cat(zs, 0) # (B*n, 1+P, E)
228
+ E = z.shape[-1]
229
+ Fb, Tw = self.n_freq, self.n_time_per_window
230
+ cls = z[:, 0].reshape(B, n_win, E)
231
+ tok = z[:, 1:].reshape(B, n_win, Fb, Tw, E).permute(0, 1, 3, 2, 4) # (B, n, t, f, E)
232
+ T = n_win * Tw
233
+ tok = tok.reshape(B, T, Fb, E)
234
+
235
+ step = c.tshape * c.hop_length / c.sampling_rate # 80 ms
236
+ t_centre = (torch.arange(T, device=tok.device, dtype=torch.float32) + 0.5) * step
237
+ n_valid_time = torch.clamp(
238
+ torch.ceil(n_valid_samples.float() / (c.tshape * c.hop_length)).long(), max=T)
239
+
240
+ if strategy == "timestamps":
241
+ hidden = tok.reshape(B, T * Fb, E)
242
+ timestamps = t_centre.repeat_interleave(Fb)
243
+ elif strategy == "frames":
244
+ hidden = tok.reshape(B, T, Fb * E)
245
+ timestamps = t_centre
246
+ elif strategy in ("mean", "cls"):
247
+ # average over the valid time steps / windows only
248
+ if strategy == "mean":
249
+ mask = (torch.arange(T, device=tok.device)[None, :] < n_valid_time[:, None]).float()
250
+ hidden = (tok.mean(2) * mask[..., None]).sum(1) / mask.sum(1, keepdim=True)
251
+ else:
252
+ n_valid_win = torch.clamp(torch.ceil(n_valid_samples.float() / win).long(), max=n_win)
253
+ mask = (torch.arange(n_win, device=tok.device)[None, :] < n_valid_win[:, None]).float()
254
+ hidden = (cls * mask[..., None]).sum(1) / mask.sum(1, keepdim=True)
255
+ timestamps = None
256
+ else:
257
+ raise ValueError("strategy must be 'timestamps', 'frames', 'mean' or 'cls'")
258
+
259
+ if not return_dict:
260
+ return hidden, timestamps
261
+ return BearingsOutput(last_hidden_state=hidden, timestamps=timestamps,
262
+ n_time=T, n_freq=Fb, n_valid_time=n_valid_time)
preprocessor_config.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "auto_map": {
3
+ "AutoFeatureExtractor": "feature_extraction_bearings.BearingsFeatureExtractor"
4
+ },
5
+ "feature_extractor_type": "BearingsFeatureExtractor",
6
+ "sampling_rate": 32000,
7
+ "window_seconds": 2.0
8
+ }