Add log-mel flux bird receipts
This commit is contained in:
parent
35a8965dfa
commit
24c1362ec8
6 changed files with 3994 additions and 3963 deletions
Binary file not shown.
|
After Width: | Height: | Size: 5.2 MiB |
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
After Width: | Height: | Size: 5.5 MiB |
File diff suppressed because it is too large
Load diff
|
|
@ -313,9 +313,11 @@ Decoded WAVs remain ignored scratch artifacts. Committed receipts live under
|
|||
`artifacts/spectra/real-birds-ffmpeg/`:
|
||||
|
||||
- `lesser-redpoll/Acanthis_cabaret_-_Lesser_Redpoll_XC482789-log-mel.png`
|
||||
- `lesser-redpoll/Acanthis_cabaret_-_Lesser_Redpoll_XC482789-log-mel-flux.png`
|
||||
- `lesser-redpoll/Acanthis_cabaret_-_Lesser_Redpoll_XC482789-cepstrum.png`
|
||||
- `lesser-redpoll/Acanthis_cabaret_-_Lesser_Redpoll_XC482789-mel-cepstral-features.csv`
|
||||
- `wattled-guan/Aburria_aburri_-_Wattled_Guan_XC250442-log-mel.png`
|
||||
- `wattled-guan/Aburria_aburri_-_Wattled_Guan_XC250442-log-mel-flux.png`
|
||||
- `wattled-guan/Aburria_aburri_-_Wattled_Guan_XC250442-cepstrum.png`
|
||||
- `wattled-guan/Aburria_aburri_-_Wattled_Guan_XC250442-mel-cepstral-features.csv`
|
||||
|
||||
|
|
@ -328,18 +330,26 @@ Invalid data found when processing input
|
|||
|
||||
The mel-cepstral receipt tool chooses the loudest active window when no start
|
||||
time is supplied, then writes log-mel, cepstrum, and per-frame feature CSV. The
|
||||
first real-bird readings:
|
||||
feature CSV now includes positive log-mel spectral flux: frame-to-frame
|
||||
new-energy motion in mel space. This is a better timing-anchor surface than the
|
||||
plain cepstrum because sustained bands recede and attacks/glides light up.
|
||||
|
||||
The first real-bird readings:
|
||||
|
||||
```text
|
||||
Lesser Redpoll active window: start=2.000s, duration=4.000s
|
||||
centroid median=2440 Hz, p90=3041 Hz, max=3981 Hz
|
||||
bandwidth median=2427 Hz, p90=2683 Hz
|
||||
peak-energy frame near 1.732s, centroid=3224 Hz, bandwidth=1768 Hz
|
||||
log-mel flux median=0.0870, p90=0.1166, p99=0.2079, max=0.3437
|
||||
top flux frames around 1.716-1.720s and 1.478-1.482s
|
||||
|
||||
Wattled Guan active window: start=137.000s, duration=4.000s
|
||||
centroid median=2217 Hz, p90=4229 Hz, max=4445 Hz
|
||||
bandwidth median=1072 Hz, p90=1854 Hz
|
||||
peak-energy frame near 1.722s, centroid=2021 Hz, bandwidth=766 Hz
|
||||
log-mel flux median=0.0749, p90=0.1034, p99=0.1818, max=0.2370
|
||||
top flux frames around 0.034-0.036s and the 3.248-3.744s high-band shifts
|
||||
```
|
||||
|
||||
Read against the current `canary-packet-trill`, the lesson is blunt:
|
||||
|
|
@ -354,6 +364,11 @@ Read against the current `canary-packet-trill`, the lesson is blunt:
|
|||
deliberately sharp, but it is still too ladder-like. Real birds are carrying
|
||||
identity through redundant contour, roughness, onset statistics, and cepstral
|
||||
envelope motion at the same time. That is the next scoring surface.
|
||||
- The log-mel flux receipts are the clearest current clue. They expose repeated
|
||||
curved strokes in Redpoll and slower phrase-boundary/harmonic-motion anchors
|
||||
in Guan. The next decoder should treat flux ridges as anchor proposals, then
|
||||
classify the surrounding log-mel/cepstral word shape instead of asking the
|
||||
cepstrum to do all the work alone.
|
||||
|
||||
## Next Cut
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,16 @@ def colorize(surface: np.ndarray, floor: float | None = None, ceiling: float | N
|
|||
return Image.fromarray(np.stack([r, g, b], axis=2).astype(np.uint8), mode="RGB")
|
||||
|
||||
|
||||
def log_mel_flux(log_mel: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
||||
if log_mel.shape[1] == 0:
|
||||
return log_mel.copy(), np.empty((0,), dtype=np.float32)
|
||||
|
||||
delta = np.diff(log_mel, axis=1, prepend=log_mel[:, :1])
|
||||
positive_delta = np.maximum(delta, 0.0)
|
||||
flux = np.sqrt(np.mean(positive_delta * positive_delta, axis=0)).astype(np.float32)
|
||||
return positive_delta.astype(np.float32), flux
|
||||
|
||||
|
||||
def select_active_window(samples: np.ndarray, sample_rate: int, duration: float) -> int:
|
||||
window = max(1, int(round(duration * sample_rate)))
|
||||
if len(samples) <= window:
|
||||
|
|
@ -124,6 +134,7 @@ def write_features(
|
|||
path: Path,
|
||||
log_mel: np.ndarray,
|
||||
cepstrum: np.ndarray,
|
||||
spectral_flux: np.ndarray,
|
||||
centers_hz: np.ndarray,
|
||||
sample_rate: int,
|
||||
hop: int,
|
||||
|
|
@ -135,7 +146,7 @@ def write_features(
|
|||
|
||||
with path.open("w", newline="", encoding="utf-8") as handle:
|
||||
writer = csv.writer(handle)
|
||||
header = ["frame", "time_seconds", "log_energy", "centroid_hz", "bandwidth_hz"]
|
||||
header = ["frame", "time_seconds", "log_energy", "centroid_hz", "bandwidth_hz", "log_mel_spectral_flux"]
|
||||
header.extend(f"cepstrum_{index}" for index in range(cepstrum.shape[0]))
|
||||
writer.writerow(header)
|
||||
for frame in range(log_mel.shape[1]):
|
||||
|
|
@ -145,6 +156,7 @@ def write_features(
|
|||
f"{math.log(max(float(energy[frame]), 1.0e-12)):.6f}",
|
||||
f"{float(centroid[frame]):.3f}",
|
||||
f"{float(bandwidth[frame]):.3f}",
|
||||
f"{float(spectral_flux[frame]):.6f}",
|
||||
]
|
||||
row.extend(f"{float(value):.6f}" for value in cepstrum[:, frame])
|
||||
writer.writerow(row)
|
||||
|
|
@ -178,20 +190,24 @@ def main() -> int:
|
|||
bank, centers = mel_filterbank(sample_rate, args.fft, args.mel_bands, args.min_hz, min(args.max_hz, sample_rate * 0.5))
|
||||
mel_power = np.maximum(bank @ power, 1.0e-18)
|
||||
log_mel = np.log(mel_power)
|
||||
flux_surface, spectral_flux = log_mel_flux(log_mel)
|
||||
cepstrum = dct_matrix(args.cepstra, args.mel_bands) @ log_mel
|
||||
|
||||
stem = args.input.stem
|
||||
log_mel_png = args.output_dir / f"{stem}-log-mel.png"
|
||||
flux_png = args.output_dir / f"{stem}-log-mel-flux.png"
|
||||
cepstrum_png = args.output_dir / f"{stem}-cepstrum.png"
|
||||
features_csv = args.output_dir / f"{stem}-mel-cepstral-features.csv"
|
||||
|
||||
width = max(900, log_mel.shape[1] * 4)
|
||||
colorize(log_mel).resize((width, 640)).save(log_mel_png)
|
||||
colorize(flux_surface, floor=0.0).resize((width, 640)).save(flux_png)
|
||||
colorize(cepstrum).resize((width, 420)).save(cepstrum_png)
|
||||
write_features(features_csv, log_mel, cepstrum, centers, sample_rate, args.hop)
|
||||
write_features(features_csv, log_mel, cepstrum, spectral_flux, centers, sample_rate, args.hop)
|
||||
|
||||
print(f"mel-cepstral input={args.input} sampleRate={sample_rate} startSeconds={start / sample_rate:.6f} samples={len(samples)}")
|
||||
print(f"logMel={log_mel_png}")
|
||||
print(f"logMelFlux={flux_png}")
|
||||
print(f"cepstrum={cepstrum_png}")
|
||||
print(f"features={features_csv}")
|
||||
return 0
|
||||
|
|
|
|||
Loading…
Reference in a new issue